# coding=utf8
import abc
import os
import zipfile


class Entry:

    @abc.abstractmethod
    def read_class(self, class_name: str) -> bytes:
        pass


def create_entry(path: str):
    if os.path.pathsep in path:
        return CompositeEntry(path)

    if path.endswith('*'):
        return WildcardEntry(path)

    if path.endswith('.jar') or path.endswith('.JAR'):
        return ZipEntry(path)

    return DirEntry(path)


class DirEntry(Entry):

    def __init__(self, path):
        self.abs_path = os.path.abspath(path)

    def read_class(self, class_name):
        class_file = os.path.join(self.abs_path, class_name)
        if os.path.isfile(class_file):
            with open(class_file, 'rb') as fp:
                return fp.read()


class ZipEntry(Entry):

    def __init__(self, path):
        self.zipfile = zipfile.ZipFile(os.path.abspath(path))

    def read_class(self, class_name):
        # noinspection PyBroadException
        try:
            return self.zipfile.read(class_name)
        except Exception as _:
            return


class CompositeEntry(Entry, list):

    def __init__(self, paths):
        list.__init__(self, [])
        if isinstance(paths, str):
            paths = paths.split(os.path.pathsep)

        for path in paths:
            self.append(create_entry(path))

    def read_class(self, class_name):
        for entry in self:
            # noinspection PyBroadException
            try:
                class_bytes = entry.read_class(class_name)
                if class_bytes:
                    return class_bytes
            except Exception as _:
                continue


class WildcardEntry(CompositeEntry):

    def __init__(self, wildcard_path):
        jars = []
        for root, _, files in os.walk(wildcard_path.replace('*', '')):
            for f in files:
                path = os.path.join(root, f)
                if path.endswith('.jar') or path.endswith('.JAR'):
                    jars.append(path)
        CompositeEntry.__init__(self, jars)


class JreFolderNotFoundError(Exception):
    pass


class UserClassPathNotFoundError(Exception):
    pass


def get_default_jre_dir():
    java_home = os.getenv('JAVA_HOME')
    if java_home:
        return os.path.join(java_home, 'jre')
    raise JreFolderNotFoundError('未找到jre路径！')


def get_jre_dir(jre_dir: str):
    if jre_dir:
        if os.path.isdir(jre_dir):
            return jre_dir
        raise JreFolderNotFoundError('无效的jre路径:%s' % jre_dir)
    return get_default_jre_dir()


def get_jre_class_entry(jre_dir: str):
    jre_dir = get_jre_dir(jre_dir)
    jre_lib_path = os.path.join(jre_dir, 'lib', '*')
    return create_entry(jre_lib_path)


def get_user_class_entry(user_class_path: str):
    if os.path.isdir(user_class_path):
        return create_entry(user_class_path)
    raise UserClassPathNotFoundError('无效的classpath:%s' % user_class_path)


class ClassNotFoundError(Exception):
    pass


class Classpath:

    def __init__(self, jre_dir: str, class_paths: str):
        entries = [get_jre_class_entry(jre_dir)]
        for path in class_paths:
            entries.append(get_user_class_entry(path))
        self.entries = entries

    def read_class_bytes(self, class_name: str) -> bytes:
        class_path = class_name.replace('.', '/') + '.class'
        for entry in self.entries:
            class_bytes = entry.read_class(class_path)
            if class_bytes:
                return class_bytes
        raise ClassNotFoundError('未找到类: %s' % class_name)


__all__ = ['Classpath']
